#include<stdio.h>
#include"fftz.h"
#include<stdlib.h>
#include<clAmdFft.h>

static int I;
static int N;
static const int REPEAT = 10;
static int time[50];
static int i, j, n;
static int currentTime;
static cl_event myevent;
static cl_ulong task_queued, task_start, task_end;
static int err;
	
int gflopsAmd(FftzEnv* cl){
	clAmdFftSetupData fft_setup_data;
	clAmdFftPlanHandle fft_plan;
	cl_mem buffer0[2]; 
	cl_mem buffer1[2];
	size_t size;
	
	buffer0[0] = clCreateBuffer(cl->context, CL_MEM_READ_WRITE, sizeof(float) * N, NULL, &err);
	if(err < 0){
		printf("Error: Failed to create buffer0[0]!\n");
		return -1;
	}
	buffer0[1] = clCreateBuffer(cl->context, CL_MEM_READ_WRITE, sizeof(float) * N, NULL, &err);
	if(err < 0){
		printf("Error: Failed to create buffer0[1]!\n");
		clReleaseMemObject(buffer0[0]);
		return -1;
	}
	buffer1[0] = clCreateBuffer(cl->context, CL_MEM_READ_WRITE, sizeof(float) * N, NULL, &err);
	if(err < 0){
		printf("Error: Failed to create buffer1[0]!\n");
		clReleaseMemObject(buffer0[0]);
		clReleaseMemObject(buffer0[1]);
		return -1;
	}
	buffer1[1] = clCreateBuffer(cl->context, CL_MEM_READ_WRITE, sizeof(float) * N, NULL, &err);
	if(err < 0){
		printf("Error: Failed to create buffer1[1]!\n");
		clReleaseMemObject(buffer0[0]);
		clReleaseMemObject(buffer0[1]);
		clReleaseMemObject(buffer1[0]);
		return -1;
	}
	
	// Get FFT version  
	err = clAmdFftInitSetupData(&fft_setup_data);
	if(err < 0){
		printf("Error: Failed to setup amdfft!\n");
		clReleaseMemObject(buffer0[0]);
		clReleaseMemObject(buffer0[1]);
		clReleaseMemObject(buffer1[0]);
		clReleaseMemObject(buffer1[1]);
		return -1;
	}
	//printf("Using clAmdFft %u.%u.%u\n",fft_setup_data.major,fft_setup_data.minor,fft_setup_data.patch);

	//FFT Setup
	err = clAmdFftSetup(&fft_setup_data);
	if(err < 0){
		printf("Error: Failed to setup amdfft!\n");
		clReleaseMemObject(buffer0[0]);
		clReleaseMemObject(buffer0[1]);
		clReleaseMemObject(buffer1[0]);
		clReleaseMemObject(buffer1[1]);
		return -1;
	}
	
	for(n = 2, i = 1; i <= I; n *= 2, i += 1){
		printf("FFT length of 2^%d start..\n", i);

		// Create FFT plan  
		size = n;
		err = clAmdFftCreateDefaultPlan(&fft_plan, cl->context, CLFFT_1D, &size);
		if(err < 0){
			printf("Error: Failed to create default plan!\n");
			clAmdFftTeardown();
			clReleaseMemObject(buffer0[0]);
			clReleaseMemObject(buffer0[1]);
			clReleaseMemObject(buffer1[0]);
			clReleaseMemObject(buffer1[1]);
			return -1;
		}		
		clAmdFftSetPlanPrecision(fft_plan, CLFFT_SINGLE);
		clAmdFftSetPlanBatchSize(fft_plan, 1);
		clAmdFftSetLayout(fft_plan, CLFFT_COMPLEX_PLANAR, CLFFT_COMPLEX_PLANAR);
		clAmdFftSetResultLocation(fft_plan, CLFFT_OUTOFPLACE);
	
		err = clAmdFftEnqueueTransform(fft_plan, CLFFT_FORWARD, 1, &cl->queue, 0, NULL, &myevent, buffer0, buffer1, NULL);
		if(err < 0){
			printf("Error: Failed to enqueue transform!\n");
			clAmdFftDestroyPlan(&fft_plan);
			clAmdFftTeardown();
			clReleaseMemObject(buffer0[0]);
			clReleaseMemObject(buffer0[1]);
			clReleaseMemObject(buffer1[0]);
			clReleaseMemObject(buffer1[1]);
			return -1;
		}
		clFinish(cl->queue);
		clGetEventProfilingInfo(myevent, CL_PROFILING_COMMAND_START, sizeof(cl_ulong), &task_start, NULL);
		clGetEventProfilingInfo(myevent, CL_PROFILING_COMMAND_END, sizeof(cl_ulong), &task_end, NULL);
		time[i] = (double)(task_end - task_start) / 1000.0;//um
		clReleaseEvent(myevent);
			
		for(j = 0; j < REPEAT - 1; j++){
			err = clAmdFftEnqueueTransform(fft_plan, CLFFT_FORWARD, 1, &cl->queue, 0, NULL, &myevent, buffer0, buffer1, NULL);
			if(err < 0){
				printf("Error: Failed to enqueue transform!\n");
				clAmdFftDestroyPlan(&fft_plan);
				clAmdFftTeardown();
				clReleaseMemObject(buffer0[0]);
				clReleaseMemObject(buffer0[1]);
				clReleaseMemObject(buffer1[0]);
				clReleaseMemObject(buffer1[1]);
				return -1;
			}
			clFinish(cl->queue);
			clGetEventProfilingInfo(myevent, CL_PROFILING_COMMAND_START, sizeof(cl_ulong), &task_start, NULL);
			clGetEventProfilingInfo(myevent, CL_PROFILING_COMMAND_END, sizeof(cl_ulong), &task_end, NULL);
			currentTime = (double)(task_end - task_start) / 1000.0;//um
			clReleaseEvent(myevent);
			if(currentTime < time[i]) time[i] = currentTime;
		}
		clAmdFftDestroyPlan(&fft_plan);
	}
		
	clAmdFftTeardown();	
	clReleaseMemObject(buffer0[0]);
	clReleaseMemObject(buffer0[1]);
	clReleaseMemObject(buffer1[0]);
	clReleaseMemObject(buffer1[1]);
	
	return 0;
}

int main(int argc, char** argv){
	FftzEnv cl;
	FftzPlan plan;
	float* data0;
	char outFile[100];
	FILE* file;
	
	if(initFftzEnv(&cl) < 0){
		printf("Initialization of FftzEnv failed, program exiting..\n");
		return -1;
	}
	
	if(argc != 3){
		printf("Argument count doesn't match, program exiting..\n");
		releaseFftzEnv(&cl);
		return -1;
	}
	I = atoi(argv[2]);
	N = 1 << I;
	
	switch(argv[1][0]){
		case 'z':
			data0 = (float*)malloc(sizeof(float) * N * 2);
			printf("FFT function selected: fftz\n");
			sprintf(outFile, "output/gflopsFftz");
			for(n = 2, i = 1; i <= I; n *= 2, i += 1){
				printf("FFT length of 2^%d start..\n", i);
				err = initFftzPlan(&cl, &plan, i);
				if(err < 0){
					printf("Initialization of FftzPlan failed with error code %d, program exitting..\n", err);
					free(data0);
					releaseFftzEnv(&cl);
					return -1;
				}
				time[i] = fftzRun(&cl, &plan, data0);;
				for(j = 0; j < REPEAT - 1; j++){
					currentTime = fftzRun(&cl, &plan, data0);
					if(currentTime < time[i]) time[i] = currentTime;
				}
				releaseFftzPlan(&plan);
			}
			free(data0);			
			break;
		case 'a':
			printf("FFT function selected: AmdClFft\n");
			sprintf(outFile, "output/gflopsAmd");
			if(gflopsAmd(&cl) < 0){
				printf("function gflopsAmd returned error, program exiting..\n");
				releaseFftzEnv(&cl);
				return -1;			
			}
			break;
		default:
			printf("Cannot recognize the first argument, program exiting..\n");
			releaseFftzEnv(&cl);
			return -1;
	}
	
	printf("Writint results to file %s.\n", outFile);
	file = fopen(outFile, "w");
	if(file == NULL){
		printf("Failed to open %s, program exiting..\n", outFile);
		releaseFftzEnv(&cl);
		return -1;
	}
	for(n = 2, i = 1; i <= I; n *= 2, i += 1){
		fprintf(file, "%d  %d  %f\n", i, time[i], (double)(5 * i * n) / time[i] / 1000);
	}
	fclose(file);
	
	releaseFftzEnv(&cl);
	return 0;
}
